﻿using SoftwareConsulting.BI11.NumericalMethods.Interfaces;

namespace SoftwareConsulting.BI11.NumericalMethods.Optimization
{
    /// <summary>
    /// Класс абстрактного генетического алгоритма
    /// </summary>
    public abstract class GeneticAlgorithm : MultiVariableOptimizer
    {	    
        private ChromosomeController _chromosomeController;

        /// <summary>
        /// Конструктор класса
        /// </summary>
        /// <param name="function">Функция, требующая решения</param>
        /// <param name="pointsCreator">Фабрика точек</param>
        /// <param name="chromosomeController">Контроллер хромосом</param>
        public GeneticAlgorithm(IManyVariableFunction function, SuspiciousOptimalPointAndVectorFactory pointsCreator, ChromosomeController chromosomeController)
                    : base(function, pointsCreator, null)
        {
            _chromosomeController = chromosomeController;
        }

        /// <summary>
        /// Добавляет указанную точку популяции
        /// </summary>
        /// <param name="newPoint"></param>
        public abstract void AddPoint(object newPoint);

        /// <summary>
        /// Сбрасывает популяцию и добавляет все точки
        /// </summary>
        public void AddAllPoints()
        {
            Reset();
            for (int i = 0; i < _chromosomeController.DesiredPopulationSize; i++)
                AddPoint(_chromosomeController.GetIndividualAt(i));
        }

        /// <summary>
        /// Возвращает случайное целочисленное значение fitness-показателя
        /// </summary>
        /// <returns></returns>
        public abstract double[] GetRandomIntegerFitness();

        /// <summary>
        /// Выполняет итерацию алгоритма
        /// </summary>
        /// <returns></returns>
        public override double DoIteration()
        {
            double[] randomFitness = GetRandomIntegerFitness();
            _chromosomeController.Reset();
            while (!_chromosomeController.IsFullyPopulated)
            {
                _chromosomeController.Process(
                                    GetIndividualAt(GetRandomIndex(randomFitness)),
                                    GetIndividualAt(GetRandomIndex(randomFitness)));
            }
            AddAllPoints();
            return 1d;
        }

        /// <summary>
        /// Возвращает указанную особь
        /// </summary>
        /// <param name="individualIndex"></param>
        /// <returns></returns>
        public abstract object GetIndividualAt(int individualIndex);

        /// <summary>
        /// Создает случайную популяцию
        /// </summary>
        public override void CreateRandomPopulation()
        {
            InitializeIterations(_chromosomeController.DesiredPopulationSize);
            _chromosomeController.RandomizePopulation();
            AddAllPoints();
        }

        /// <summary>
        /// Инициализирует итерации
        /// </summary>
        /// <param name="initialPopulationSize"></param>
        public abstract void InitializeIterations(int initialPopulationSize);

        /// <summary>
        /// Возвращает случайный индекс по указанному fitness-коэффициенту
        /// </summary>
        /// <param name="fitnessScale"></param>
        /// <returns></returns>
        protected int GetRandomIndex(double[] fitnessScale)
        {
            double rand = _chromosomeController.GetNextRandomDouble();
            if (rand < fitnessScale[0])
                return 0;
            int n = 0;
            int m = fitnessScale.Length;
            while (n < m - 1)
            {
                int k = (n + m) / 2;
                if (rand < fitnessScale[k])
                    m = k;
                else
                    n = k;
            }
            return m;
        }        

        /// <summary>
        /// Сбрасывает популяцию
        /// </summary>
        public abstract void Reset();
    }
}
